package cc.shacocloud.mirage.bean;

import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 通用工厂加载机制，供框架内内部使用
 *
 * @author 思追(shaco)
 */
@Slf4j
public final class MirageFactoriesLoader {
    
    /**
     * 在多个 JAR 文件中寻找工厂配置的位置
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/mirageFactories.properties";
    
    /**
     * 工厂自动配置类的键
     */
    public static final String FACTORIES_CONFIGURATION_KEY = "factories.autoConfiguration";
    
    /**
     * 工厂排除指定组件的键
     */
    public static final String FACTORIES_EXCLUDE_COMPONENT_KEY = "factories.excludeComponent";
    
    static final Map<ClassLoader, Map<String, Set<String>>> cache = new ConcurrentReferenceHashMap<>();
    
    private MirageFactoriesLoader() {
    }
    
    /**
     * 加载工厂通过 {@link #FACTORIES_RESOURCE_LOCATION} 配置 {@link #FACTORIES_EXCLUDE_COMPONENT_KEY} 的配置类
     *
     * @param classLoader 用于加载的类加载器（可以是 {@code null} 以使用默认值）
     * @see #loadFactoryNames
     * @see #FACTORIES_RESOURCE_LOCATION
     * @see #FACTORIES_EXCLUDE_COMPONENT_KEY
     */
    @NotNull
    public static Set<Class<?>> loadFactoriesExcludeComponent(@Nullable ClassLoader classLoader) {
        return loadFactories(FACTORIES_EXCLUDE_COMPONENT_KEY, classLoader);
    }
    
    /**
     * 加载工厂通过 {@link #FACTORIES_RESOURCE_LOCATION} 配置 {@link #FACTORIES_CONFIGURATION_KEY} 的配置类
     *
     * @param classLoader 用于加载的类加载器（可以是 {@code null} 以使用默认值）
     * @see #loadFactoryNames
     * @see #FACTORIES_RESOURCE_LOCATION
     * @see #FACTORIES_CONFIGURATION_KEY
     */
    @NotNull
    public static Set<Class<?>> loadFactoriesConfiguration(@Nullable ClassLoader classLoader) {
        return loadFactories(FACTORIES_CONFIGURATION_KEY, classLoader);
    }
    
    /**
     * 使用指定的类加载器从 {@link #FACTORIES_RESOURCE_LOCATION} 加载并实例化给定类型的工厂实现。
     *
     * @param factoryKey  工厂的配置键
     * @param classLoader 用于加载的类加载器（可以是 {@code null} 以使用默认值）
     * @see #loadFactoryNames
     */
    @NotNull
    public static Set<Class<?>> loadFactories(@NotNull String factoryKey, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = MirageFactoriesLoader.class.getClassLoader();
        }
        
        Set<String> factoryValues = loadFactoryNames(factoryKey, classLoaderToUse);
        if (log.isTraceEnabled()) {
            log.trace("加载工厂配置键 {}，得到：{} ", factoryKey, factoryValues);
        }
        
        final ClassLoader finalClassLoaderToUse = classLoaderToUse;
        return factoryValues.stream()
                .map(className -> {
                    try {
                        return finalClassLoaderToUse.loadClass(className);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(String.format("无法加载工厂配置类：%s", className));
                    }
                })
                .collect(Collectors.toSet());
    }
    
    /**
     * 加载指定工厂的
     *
     * @see #loadFactories
     */
    public static Set<String> loadFactoryNames(@NotNull String factoryKey, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = MirageFactoriesLoader.class.getClassLoader();
        }
        
        return loadFactories(classLoaderToUse).getOrDefault(factoryKey, Collections.emptySet());
    }
    
    /**
     * 基于类加载器加载对应的配置信息
     */
    @NotNull
    private static Map<String, Set<String>> loadFactories(@NotNull ClassLoader classLoader) {
        Map<String, Set<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
        
        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                
                Properties properties = new Properties();
                properties.load(url.openStream());
                
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryType = (String) entry.getValue();
                    if (StrUtil.isBlank(factoryType)) continue;
                    
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    
                    List<String> factoryImplementationNames = StrUtil.split(factoryType,
                            ",", true, true);
                    
                    for (String factoryImplementationName : factoryImplementationNames) {
                        if (StrUtil.isEmpty(factoryImplementationName)) continue;
                        
                        result.computeIfAbsent(factoryTypeName, key -> new HashSet<>())
                                .add(factoryImplementationName);
                    }
                }
            }
            
            cache.put(classLoader, result);
        } catch (IOException ex) {
            throw new IllegalArgumentException("从 [" + FACTORIES_RESOURCE_LOCATION + "] 加载工厂配置发生例外！", ex);
        }
        return result;
    }
    
}
